#!/usr/bin/env ruby

# Copyright (c) 2021-2025 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include_relative 'common.irt'

module Constants
    SB_VALUE_OFFSET = "ark::ObjectHeader::ObjectHeaderSize()"
    SB_COUNT_OFFSET = SB_VALUE_OFFSET + " + ark::OBJECT_POINTER_SIZE"
    NULLSTR = "0x006c006c0075006e"
    NULLSTR_LEN = "4"
    TRUESTR = "0x0065007500720074"
    FALSESTR = "0x0073006c00610066"
    FALSESTR2 = "0x0065"
    BOOLSTRLEN = "5"
end

function(:StringBuilderBool,
          params: {sb: 'ref', tv: 'u8'},
          regmap: $full_regmap,
          regalloc_set: $panda_mask,
          mode: [:FastPath]) {
    # Arm32 is not supported
    if Options.arch == :arm32
        Intrinsic(:UNREACHABLE).void.Terminator
        next
    end

    value := LoadI(sb).Imm(Constants::SB_VALUE_OFFSET).ref
    value := Cast(value).SrcType(Constants::COMPILER_REFERENCE).ptr
    count := LoadI(sb).Imm(Constants::SB_COUNT_OFFSET).u32
    size := LoadI(value).Imm(Constants::ARRAY_LENGTH_OFFSET).u32
    length := Cast(Constants::BOOLSTRLEN).u32
    # len("false") == 5, len("true") == len("false") - (int)true
    length := Sub(length, Cast(tv).u32).u32

    new_count := Add(count, length).u32
    If(size, new_count).LT.Unlikely.b {
        ep_offset = get_entrypoint_offset("STRING_BUILDER_BOOL_SLOW_PATH");
        Intrinsic(:SLOW_PATH_ENTRY, sb, tv).AddImm(ep_offset).MethodAsImm("StringBuilderBoolUsualBridge").Terminator.ref
        Intrinsic(:UNREACHABLE).Terminator.void
    }

    buf := AddI(value).Imm(Constants::ARRAY_DATA_OFFSET).ptr
    buf := Add(buf, ShlI(count).Imm(1).u32).ptr

    compare := Compare(tv, 1).EQ.b
    IfImm(compare).Imm(0).NE.b {
        v := Constants::TRUESTR
        StoreI(buf, v).Imm(0).u64
    } Else {
        v := Constants::FALSESTR
        v2 := Constants::FALSESTR2
        StoreI(buf, v).Imm(0).u64
        StoreI(buf, v2).Imm(8).u16
    }

    StoreI(sb, new_count.u32).Imm(Constants::SB_COUNT_OFFSET).u32
    Return(sb).ref
}

function(:StringBuilderChar,
          params: {sb: 'ref', ch: 'u16'},
          regmap: $full_regmap,
          regalloc_set: $panda_mask,
          mode: [:FastPath]) {
    # Arm32 is not supported
    if Options.arch == :arm32
        Intrinsic(:UNREACHABLE).void.Terminator
        next
    end

    value := LoadI(sb).Imm(Constants::SB_VALUE_OFFSET).ref
    value := Cast(value).SrcType(Constants::COMPILER_REFERENCE).ptr
    count := LoadI(sb).Imm(Constants::SB_COUNT_OFFSET).u32
    size := LoadI(value).Imm(Constants::ARRAY_LENGTH_OFFSET).u32
    If(size, count).LE.Unlikely.b {
        ep_offset = get_entrypoint_offset("STRING_BUILDER_CHAR_SLOW_PATH");
        Intrinsic(:SLOW_PATH_ENTRY, sb, ch).AddImm(ep_offset).MethodAsImm("StringBuilderCharUsualBridge").Terminator.ref
        Intrinsic(:UNREACHABLE).Terminator.void
    }

    buf := AddI(value).Imm(Constants::ARRAY_DATA_OFFSET).ptr
    Store(buf, ShlI(count).Imm(1).u32, ch).u16
    StoreI(sb, AddI(count).Imm(1).u32).Imm(Constants::SB_COUNT_OFFSET).u32

    Return(sb).ref
}

def GenerateStringBuilderString(compression)
    suffix = compression ? "Compressed" : ""
    function("StringBuilderString#{suffix}".to_sym,
          params: {sb: 'ref', str: 'ref'},
          regmap: $full_regmap,
          regalloc_set: $panda_mask,
          mode: [:FastPath]) {

    # Arm32 is not supported
    if Options.arch == :arm32
        Intrinsic(:UNREACHABLE).void.Terminator
        next
    end

    value := LoadI(sb).Imm(Constants::SB_VALUE_OFFSET).ref
    value := Cast(value).SrcType(Constants::COMPILER_REFERENCE).ptr
    count := LoadI(sb).Imm(Constants::SB_COUNT_OFFSET).u32
    size := LoadI(value).Imm(Constants::ARRAY_LENGTH_OFFSET).u32
    dst := AddI(value).Imm(Constants::ARRAY_DATA_OFFSET).ptr

    # str is nullptr, store the "null" string with the length of 4
    compare := Compare(str, 0).SrcType(Constants::COMPILER_REFERENCE).EQ.b
    IfImm(compare).Imm(0).SrcType(Constants::COMPILER_BOOL).NE.Unlikely.b {
        new_count := AddI(count).Imm(Constants::NULLSTR_LEN).u32
        If(new_count, size).GT.Unlikely.b {
            Goto(:CallCppIntrinsic)
        }
        nullstr := Constants::NULLSTR

        Store(dst, ShlI(count).Imm(1).u32, nullstr).u64
        StoreI(sb, new_count).Imm(Constants::SB_COUNT_OFFSET).Volatile.u32
    } Else {
        length := LoadI(str).Imm(Constants::STRING_LENGTH_OFFSET).u32
        compare := Compare(length, 0).EQ.b
        IfImm(compare).Imm(0).NE.Unlikely.b {
            Goto(:Exit)
        }

        utf16 := Cast(1).u32

        if compression
            utf16 := AndI(length).Imm(1).u32
            length := ShrI(length).Imm(Constants::STRING_LENGTH_SHIFT).u32
        end

        # do not do compressed string unpacking
        If(utf16, 0).EQ.Unlikely.b {
            Goto(:CallCppIntrinsic)
        }

        new_count := Add(count, length).u32
        If(new_count, size).GT.Unlikely.b {
            Goto(:CallCppIntrinsic)
        }

        compare := Compare(length, 8).GT.b
        IfImm(compare).Imm(0).NE.Unlikely.b {
            Goto(:CallCppIntrinsic)
        }

        count := ShlI(count).Imm(1).u32
        src := Cast(str).SrcType(Constants::COMPILER_REFERENCE).ptr
        src := AddI(src).Imm(Constants::STRING_DATA_OFFSET).ptr
        dst := Add(dst, count).ptr

        compare := Compare(length, 4).GT.b
        IfImm(compare).Imm(0).NE.b {
            Goto(:SizeGT4)
        }

        compare := Compare(length, 4).EQ.b
        IfImm(compare).Imm(0).NE.b {
            b := LoadI(src).Imm(0).u64
            StoreI(dst, b).Imm(0).u64
            Goto(:StoreCount)
        }

        compare := Compare(length, 1).EQ.b
        IfImm(compare).Imm(0).NE.b {
            b := LoadI(src).Imm(0).u16
            StoreI(dst, b).Imm(0).u16
            Goto(:StoreCount)
        }
        
        b := LoadI(src).Imm(0).u32
        compare := Compare(length, 2).EQ.b
        IfImm(compare).Imm(0).NE.b {
            StoreI(dst, b).Imm(0).u32
            Goto(:StoreCount)
        }

        b1 := LoadI(src).Imm(4).u16
        StoreI(dst, b).Imm(0).u32
        StoreI(dst, b1).Imm(4).u16
        Goto(:StoreCount)

Label(:SizeGT4)
        sz := ShlI(length).Imm(1).u32
        b1 := LoadI(src).Imm(0).u64
        b2 := Load(src, SubI(sz).Imm(8).u32).u64
        StoreI(dst, b1).Imm(0).u64
        Store(dst, SubI(sz).Imm(8).u32, b2).u64

Label(:StoreCount)        
        StoreI(sb, new_count).Imm(Constants::SB_COUNT_OFFSET).Volatile.u32
    }

Label(:Exit)
    Return(sb).ref

Label(:CallCppIntrinsic)
    ep_offset = get_entrypoint_offset("STRING_BUILDER_STRING_SLOW_PATH");
    Intrinsic(:SLOW_PATH_ENTRY, sb, str).AddImm(ep_offset).MethodAsImm("StringBuilderStringUsualBridge").Terminator.ref
    Intrinsic(:UNREACHABLE).Terminator.void

}
end

include_plugin 'ets_string_builder'

GenerateStringBuilderString(true)
GenerateStringBuilderString(false)
